/*
	XMLeD - A browser based XML aware WYSIWIG Editor
    Copyright (C) 2012  Alex Griessel - DITALabs

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.


	ToDo
	========

	1. Complete XML Schema for DITA in JSON Schema
	2. Write Schema generator for DTD's and XSD's
	3. Clean up and refactor code
	4. Convert to JS Framework neutral
	5. Many many other things
*/



(function ($) {
	
	var me, editor, statusbar, toolbar, xmledconsole, contextmenucontrols;
	var methods = {
		init : function( options ) {
			return this.each(function() {
				
				var $this = $(this),
		             data = $this.data('xmled'),
		             xmled = $('<div />', {
		               text : $this.attr('title')
		             });
				
		         if ( ! data ) {
						
						me = this;
						//add toolbar
						toolbar = $(this).append("<div class='toolbar'><table><tr><td><a href='#b' class='xmled-btn xmled-btn-bold'></a></td><td><a href='#i' class='xmled-btn xmled-btn-italic'></a></td><td><a href='#u' class='xmled-btn xmled-btn-underline'></a></td><td><a href='#ul' class='xmled-btn xmled-btn-ul'></a></td><td><a href='#ol' class='xmled-btn xmled-btn-ol'></a></td><td><a href='#validate' class='xmled-btn xmled-btn-validate'></a></td></tr></table></div>");

						//adding tb events
						$('.toolbar a',this).bind('click', function(ev) {
							var optionTemplate = $(this).xmled('getTemplate', {'instance' : me, 'id' : ev.target.hash});
							$(this).xmled(optionTemplate.action,{'text': optionTemplate.template,'instance': me, className : optionTemplate.className});
							ev.preventDefault();
						});
						
						//add editor area
						editor = $(this).append("<div class='editor'>Loading</div>");

						//add footer
						statusbar = $(this).append("<div class='statusbar'></div>");

						//context menu
						contextmenucontrols = $(this).append('<div class="controls"><div class="contextMenu" id="editorMenu"><ul><li id="insert"><a href="#b">Bold</a></li><li id="insert"><a href="#i">Italic</a></li><li id="insert"><a href="#u">Underline</a></li><li id="insert"><a href="#keyword">Keyword</a></li></ul></div></div>');
						
						// editors console
						xmledconsole = $(this).append("<div class='console'></div>");
						
						// Load XML if any
						if (options.xml) {
							$(this).xmled('loadXML', options.xml);
						}


						// binding events to context menu
						// TODO, obtain valid context options from JSON Schema
						$(this).contextMenu('editorMenu', {
                   			 bindings: {
                        		'insert': function(t) {
									if ( window.getSelection().toString() != '' ) {
                            			var anchor = window.event.currentTarget.firstChild;
                            			var optionTemplate = $('.xmled').xmled('getTemplate', {'instance' : this, 'id' : anchor.hash});
										//Don't allow recursive nesting
										if ( window.getSelection().anchorNode.parentNode.nodeName != optionTemplate.template.toUpperCase() ) {
                        	    			$('.xmled').xmled(optionTemplate.action,{'text': optionTemplate.template,'instance': this, className : optionTemplate.className});
										} else {
                        	    			$('.xmled').xmled('unWrapAtCursor',{'text': optionTemplate.template,'instance': this, className : optionTemplate.className});
										}
									}
                        		}
                    		}
                		});
						
						// Make content Editable
						$('.editor', this).attr('contentEditable','true');
						
						// Handle Key Down events
						editor.bind('keyup', function(ev) {
								if (ev.keyCode >= 37 && ev.keyCode <= 40) {
									var sel, range, html;
								    if (window.getSelection) {
								        sel = window.getSelection();
								        if (sel.getRangeAt && sel.rangeCount) {
								            range = sel.getRangeAt(0);
											
								            var contextNode = sel.anchorNode.parentNode;
											var nodePath = [];
											$(".active").removeClass("active");
											$(contextNode).addClass('active');

											while (contextNode.parentNode && /topic/.test(contextNode.className) ) {
												nodePath.push(contextNode.nodeName);
												contextNode = contextNode.parentNode;
											}
											
											$('.statusbar',this).html(nodePath.reverse().toPath());
								        }
								    } else if (document.selection && document.selection.createRange) {
								        document.selection.createRange().text = options.text;
								    }
								}
								
						});
						editor.bind('keydown', function(ev) {
							if (ev.keyCode == 13) {
								var sel, range, html;
							    if (window.getSelection) {
							        sel = window.getSelection();
							        if (sel.getRangeAt && sel.rangeCount) {
							            range = sel.getRangeAt(0);
										var cls = sel.anchorNode.parentNode.className;
										switch(true) {
											case /topic\/li/.test(cls):
												$(sel.anchorNode.parentNode).after('<dita:li class="- topic/li empty ">Enter text here</dita:p>');
												break;
											case /topic\/p/.test(cls):
												$(sel.anchorNode.parentNode).after('<dita:p class="- topic/p empty ">Enter text here</dita:p>');
												break;
											default:
												ev.preventDefault();
												return;
										} 
									}
								}
								
								$(this).xmled('moveCaret', { charCount : 4, newNode : sel.anchorNode.parentNode.nextSibling});
								var contextNode = sel.anchorNode.parentNode;
								var nodePath = [];
								$(".active").removeClass("active");

								while (contextNode.parentNode && /topic/.test(contextNode.className) ) {
									nodePath.push(contextNode.nodeName);
									contextNode = contextNode.parentNode;
								}
								
								$('.statusbar',this).html(nodePath.reverse().toPath());
								ev.preventDefault();
							} else {
								var sel, range, html;
							    if (window.getSelection) {
							        sel = window.getSelection();
							        if (sel.getRangeAt && sel.rangeCount) {
							            range = sel.getRangeAt(0);
										var cls = sel.anchorNode.parentNode.className;
										if ($(sel.anchorNode.parentNode).hasClass('empty'))  {
											$(sel.anchorNode.parentNode).removeClass('empty');
											$(sel.anchorNode.parentNode).html("");
										} 
									}
								}

								if ($(sel.anchorNode.parentNode).html().length == 1 && ev.keyCode == 8) {
										$(this).xmled('moveCaret', { charCount : 4, newNode : sel.anchorNode.parentNode.previousSibling});
										$(sel.anchorNode.parentNode.nextSibling).remove();
										ev.preventDefault();
								}
							}
							
							
						});
						
						//Handle Mouse over events
						$('.editor',this).bind('mouseover', function(ev) {
							$(".active").removeClass("active");
							$(ev.target).addClass('active');
							
						});
						
						//Handle context menu
						$(this).bind('contextmenu', function(ev) {
							/*var listItem = document.createElement("li");
							var itemText = document.createTextNode(ev.target.className);
							listItem.appendChild(itemText);
							$('.console', this).append(listItem);*/
							ev.preventDefault();
						});

						//Handle Click events
						$(this).bind('click', function(ev) {
							var contextNode = ev.target;
							var nodePath = [];
								$(".active").removeClass("active");

							while (contextNode.parentNode && /topic/.test(contextNode.className) ) {
								nodePath.push(contextNode.nodeName);
								contextNode = contextNode.parentNode;
							}

							$('.statusbar',this).html(nodePath.reverse().toPath());

						});
					}
				});
			
	    },
		moveCaret : function ( options ) {
		    var sel, range;
		    if (window.getSelection) {
		        sel = window.getSelection();
		        if (sel.rangeCount > 0) {
		            var textNode = sel.focusNode;
		            var newOffset = sel.focusOffset + options.charCount;
					if ( $(options.newNode).hasClass("empty") ) {
		            	sel.collapse(options.newNode, 0);
					} else {
			            sel.collapse(options.newNode, $(options.newNode).html().length - 1);
					}
		        }
		    } else if ( (sel = window.document.selection) ) {
		        if (sel.type != "Control") {
		            range = sel.createRange();
		            range.move("character", options.charCount);
		            range.select();
		        }
		    }
		},
		// ToDo - Remove parent element from text
		unWrapAtCursor : function (options)  {
			var sel, range, html;
		    if (window.getSelection) {
		        sel = window.getSelection();
		        if (sel.getRangeAt && sel.rangeCount) {
		            range = sel.getRangeAt(0);
					var currentSelection = range.extractContents();
					console.log(currentSelection.innerHTML);
					range.insertNode(currentSelection);
		        }
		    } else if (document.selection && document.selection.createRange) {
		        document.selection.createRange().text = "<" + options.text + " class='- topic/" + options.text + "'>" + document.selection.createRange().text + "</" + options.text + ">";
		    }
		},
		wrapAtCursor : function (options)  {
			var sel, range, html;
		    if (window.getSelection) {
		        sel = window.getSelection();
		        if (sel.getRangeAt && sel.rangeCount) {
		            range = sel.getRangeAt(0);
					wrapper = document.createElement(options.text)
					$(wrapper).addClass(options.className)
					range.surroundContents(wrapper)
		        }
		    } else if (document.selection && document.selection.createRange) {
		        document.selection.createRange().text = "<" + options.text + " class='- topic/" + options.text + "'>" + document.selection.createRange().text + "</" + options.text + ">";
		    }
		},
		insertAtCursor : function (options)  {
		    var sel, range, html;
		    if (window.getSelection) {
		        sel = window.getSelection();
		        if (sel.getRangeAt && sel.rangeCount) {
		            range = sel.getRangeAt(0);
		            range.deleteContents();
		            range.insertNode( options.text );
		        }
		    } else if (document.selection && document.selection.createRange) {
		        document.selection.createRange().text = options.text;
		    }
		},
		jsonTopic : null,
		loadXML : function(topic) {
			var virtualEl = document.createElement('div');
			virtualEl.innerHTML = topic;
			var ditaTopic = JSON.parse(xml2json(virtualEl.firstChild,' '));

			this.jsonTopic = ditaTopic;
			var xml = json2xml(ditaTopic, '');
			$(".editor", this).html(xml);
		},
		xmlTemplates : {
		},
		getTemplate : function(options) {
			var templates = {
				'#ul' : {
						action : 'insertAtCursor',
						template : "<dita:ul class='- topic/ul '><dita:li class='- topic/li empty '>Item</dita:li></dita:ul>"
					},
				'#ol' : {
						action : 'insertAtCursor',
						template :"<dita:ol class='- topic/ol '><dita:li class='- topic/li empty '>Item</dita:li></dita:ol>",
					},
				'#b' : {
					action :'wrapAtCursor',
					className : '- topic/ph hi-d/b ',
					template : 'dita:b'
				},
				'#i' : {
					action :'wrapAtCursor',
					className : '- topic/ph hi-d/i ',
					template : 'dita:i'
				},
				'#u' : {
					action :'wrapAtCursor',
					className : '- topic/ph hi-d/u ',
					template : 'dita:u'
				},
				'#keyword' : {
					action :'wrapAtCursor',
					className : '- topic/keyword ',
					template : 'dita:keyword'
				},
				'#validate' : {
					action : 'validate',
					template : ''
				}
				
			}
			if (templates[options.id].action == "insertAtCursor") {
				var vElement = document.createElement("div");
				vElement.innerHTML = templates[options.id].template;
				return { action : templates[options.id].action, template : vElement.firstChild};
			} else {
				return templates[options.id];
			} 
		},
		getSchema : function(options) {
		var	schemas = {
				concept : { 
					type : 'object',
					properties : {
						'TOPIC' : {
							type : 'object',
							properties : {
								'@id' : {
									type : 'string',
									required : true
								},
								'@class' : {
									type : 'string'
								},
								'TITLE' : {
									type : 'object',
									properties : {
										type : 'any'
									}
								},
								'SHORTDESC' : {
									type : 'object'
								},
								'BODY' : {
									type : 'object',
									properties : {
										'@class' : {
											type : 'string'
										},
										'P' : {
											type : 'any',
											properties : {
												'@class' : {
													'type' : 'string'
												}
											},
											'KEYWORD' : {
												type : 'string',
												properties : {
													'@class' : {
														'type' : 'string'
													}
												}
											}
										}	
									}
								}
							}
						}
					}

				}
			};
			return schemas.concept;
		},
	   	validate : function( options ) {
			var env = JSV.createEnvironment();
			var vEl = document.createElement('div');
			vEl.innerHTML = $(".editor").html();
			var ditaTopic = JSON.parse(xml2json( vEl.firstChild,' '));
			var schema = $(this).xmled('getSchema');
			var report = env.validate(ditaTopic, schema);
			$('.console',$(options.instance)).append("<li>Validation errors " + report.errors.length + "</li>");
			if (report.errors.length) {
				var element = report.errors[0].uri.split('/');
				$('.console' , me).append("<li> " + report.errors[0].message + " :: " + element[element.length-1] + "</li>");
			}
		},
		destroy : function( ) {

		       return this.each(function(){
		         $(window).unbind('.xmled');
		       })

		     },
	
	  };
	
	$.fn.xmled = function(method) {
		if ( methods[method] ) {
			return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.xmled' );
		}
	};
})(jQuery);

Array.prototype.toPath = function() {
	var retPath = "";
	for (i in this) {
		if (this.hasOwnProperty(i)) {
			retPath += this[i] + ((i < this.length - 1) ? " > " : "");
		}
	}
	return retPath;
}
